/* Copyright 2008 Bas van den Berg
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "MudPlayer.h"
#include "Session.h"
#include "StringBuilder.h"
#include "CmdVisitor.h"
#include "Utils.h"
#include "MudCredits.h"
#include "MudItem.h"
#include "MudWearable.h"
#include "Room.h"
#include "Error.h"
#include "Utils.h"
#include "Database.h"
#include "myassert.h"
#include "ItemDb.h"
#include "Global.h"
#include "Trade.h"
#include "PlayerSettings.h"

using namespace Chronos;
using namespace std;

MudPlayer::MudPlayer(Session& session_, int id_, const Stats& stats_) 
    : MudMobile(session_.getName(), "a player.", stats_)
    , session(session_)
    , databaseID(id_)
	, credits(0)
    , trade(0)
{}


VISITOR_ACCEPT(MudPlayer)


const string& MudPlayer::getOption(const string& setting) const {
    return session.getSettings().getOption(setting);
}


void MudPlayer::write(const string& text) {
    session.write() << text;
}


void MudPlayer::generateEvent(MudObjectEvent event) {
    switch(event) {
    case OBJECT_DESTRUCTION:
        ASSERT_FAIL();
        break;
    case MUDMOBILE_DEATH:
    case MUDMOBILE_MOVEMENT:
    case MUDMOBILE_START_COMBAT:
    case MUDPLAYER_LOGOUT:
        if (trade != 0) trade->cancel();
        cancelActivity();
        break;
    default:
        break;
    }
    MudMobile::generateEvent(event);
}


unsigned int MudPlayer::doCombat() {
    if (!inCombat) return 0;
   
    if (combatTarget->getRoom() != this->room) {
        return stats[Stats::speed];
    }

    if (Utils::roll(1,100) <= stats[Stats::hitChance]) {
        int dmg = Utils::roll(1, stats[Stats::maxDamage]);
		string combatName = combatTarget->getName();
        session.write() << "You hit " << combatTarget->getName() << " for " << dmg << ".\n";
        room->showText(name + " hits " + combatName + " for " + Utils::itoa(dmg) + ".", *this, *combatTarget);
        if (!combatTarget->takeDmg(*this, dmg)) {
			session.write() << "You kill " << combatName << ".\n";
			return 0;
		}
    } else {
        session.write() << "You miss " << combatTarget->getName() << ".\n";
        room->showText(name + " misses " + combatTarget->getName() + ".", *this, *combatTarget);
        combatTarget->miss(*this);
    }
    return stats[Stats::speed];
}


void MudPlayer::miss(MudMobile& source) {
    session.write() << source.getName() << " misses you.\n"; 
    addTarget(source);
}


bool MudPlayer::takeDmg(MudMobile& source, unsigned int dmg) {
    session.write() << source.getName() << " hits you for " << dmg << ".\n";
    addTarget(source);
    stats.modifyHealth(-dmg);
    if (stats.getHealth() <= 0) {
        session.write() << "You die.\n";
		string cause = "killed by " + source.getName();
		room->showText(name + " is " + cause + ".", *this, source);
        die(cause);
        cancelActivity();
        session.write() << "You are instantly resurrected to full health..now behave yourself this time!\n";
        stats.setHealth(stats[Stats::maxHealth]);
		return false;
    }
	return true;
}


void MudPlayer::addItem(MudItem* item) {
    MudCredits* creditItem = TypeVisitor<MudCredits>::getType(*item);
	if (creditItem != 0) {
		credits	+= creditItem->getAmount();
		delete item;
		return;
	} 
    inventory.addItem(item);
}


MudItem* MudPlayer::findItem(const string& name) const {
    MudItem* item = inventory.findItem(name);
    if (item) return item;
    item = equipment.findItem(name);
    return item;
}


MudItem* MudPlayer::findInInventory(const string& name) const {
    MudItem* item = inventory.findItem(name);
    return item;
}


MudWearable* MudPlayer::findInEquipment(const string& name) const {
    MudWearable* item = equipment.findItem(name);
    return item;
}


void MudPlayer::removeItem(const MudItem* item) {
    if (inventory.findItem(item->getName())) {
        inventory.removeItem(item);
        return;
    }
    MudWearable* equip = equipment.findItem(item->getName());
    if (equip) {
        unwear(equip);
        inventory.removeItem(item);
        return;
    }
    throw MudError("MudPlayer: item " + item->getName() + " unknown");
}


void MudPlayer::visitInventory(ConstItemVisitor& visitor) const {
    inventory.visitItems(visitor);
}


void MudPlayer::visitInventory(ItemVisitor& visitor) {
    const ItemContainer::Items& items = inventory.getItems();
    for (ItemContainer::ItemsConstIter iter = items.begin(); iter != items.end(); ++iter) {
        visitor.handle(*iter);
    }
}


void MudPlayer::wear(MudWearable* equip) {
    MudWearable* oldEquip = equipment[equip->getSlot()];
    if (oldEquip) unwear(oldEquip);
    session.write() << "OK\n";
    room->showText(getName() + " wears " + equip->getName() + ".");
    inventory.removeItem(equip);
    equipment.wear(equip);
    equip->applyBonus(stats);
}


void MudPlayer::unwear(MudWearable* equip) {
    session.write() << "removing " << equip->getName() << ".\n";
    room->showText(getName() + " removes " + equip->getName() + ".");
    equipment.remove(equip);
    equip->unapplyBonus(stats);
    inventory.addItem(equip);
}

const Equipment& MudPlayer::getEquipment() const { return equipment; }

void MudPlayer::setActivity(Activity* activity) { activityManager.setActivity(activity); }

bool MudPlayer::cancelActivity() { return activityManager.cancelActivity(); }


unsigned int MudPlayer::getCredits() const { return credits; }

void MudPlayer::setCredits(unsigned int amount) { credits = amount; }

void MudPlayer::addCredits(int amount) { credits += amount; }

int MudPlayer::getID() const { return databaseID; }

bool MudPlayer::inTrade() const { return trade != 0; }

void MudPlayer::setTrade(Trade* trade_) { trade = trade_; }

Trade* MudPlayer::getTrade() const { return trade; }


void MudPlayer::save(Database& database) const {
    StringBuilder statement;
    statement << "update players set stats='"; stats.serialize(statement);
    statement << "', credits=" << credits;
    statement << ", equipment='"; equipment.serialize(statement);
    statement << "', inventory='"; inventory.serialize(statement);
    statement << "' where id=" << databaseID << ';'; 
    database.query(statement);
}


MudPlayer& MudPlayer::load(Database& database, const string& name, Session& session) {
    StringBuilder statement;
    statement << "select id, stats, inventory, equipment, credits from players where name='" << name << "' LIMIT 1;";
    Result result;
    database.query(statement, result);
    ASSERT_EQUAL(5, result.size());
    int id = result.getIntResult(0);
    Stats stats(result[1]);
    MudPlayer* player = new MudPlayer(session, id, stats);
    player->deserializeInv(result[2].c_str());
    player->deserializeEquipment(result[3].c_str());
    int credits = result.getIntResult(4);
    player->setCredits(credits);

    return *player;
}

static const char* ITEMLIST_DELIMITER = ";";

void MudPlayer::deserializeInv(const char* input) {
    char buffer[strlen(input)+1];
    strcpy(buffer, input);
    char* saveptr = 0;
    char* objectString = strtok_r(buffer, ITEMLIST_DELIMITER, &saveptr);
    while (objectString) {
        MudItem* item = Global<ItemDb>()->create(atoi(objectString));
        addItem(item);
        objectString = strtok_r(0, ITEMLIST_DELIMITER, &saveptr);
    }
}


void MudPlayer::deserializeEquipment(const char* input) {
    char buffer[strlen(input)+1];
    strcpy(buffer, input);
    char* saveptr = 0;
    char* objectString = strtok_r(buffer, ITEMLIST_DELIMITER, &saveptr);
    while (objectString) {
        MudItem* item = Global<ItemDb>()->create(atoi(objectString));
        MudWearable* equip = TypeVisitor<MudWearable>::getType(*item);
        ASSERT_NOT_NULL(equip);
        equipment.wear(equip);
        equip->applyBonus(stats);
        objectString = strtok_r(0, ITEMLIST_DELIMITER, &saveptr);
    }
}


void MudPlayer::create(Database& database, const string& name, const string& password) {
    database.addPlayer(name, password);
    Stats stats;
    stats.set(Stats::maxHealth, 100);
    stats.set(Stats::speed, 10);
    stats.set(Stats::maxDamage, 10);
    stats.set(Stats::hitChance, 80);
    stats.setHealth(100);
    StringBuilder statement;
    statement << "update players set stats='"; stats.serialize(statement);
    statement << "', credits=0 where name='" << name << "';",
    database.query(statement);
}

